home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-04-12 | 39.8 KB | 967 lines | [TEXT/MPS ] |
- The title of this document should have been:
-
- How to write your application using AppWannabe (or Wannabe for short).
-
- The above is too long for a file name, but obviously the title I used got your
- attention. Now all I have to do is to hold it...
-
- If you have built the libraries and sample applications, you may have already
- noted that Wannabe is actually an executable application. Of course, it does
- absolutely nothing useful. That's your job.
-
- I am assuming that you have built the libraries and Wannabe.
-
- If you use MPW, you built the libraries DTS.Lib_controls, DTS.Lib_ctlhandler,
- DTS.Lib_framework, DTS.Lib_strings, DTS.Lib_treeobj, DTS.Lib_utils.
-
- If you use Think, you built the libraries DTS.Lib..controls, DTS.Lib..ctlhandler,
- DTS.Lib..framework, DTS.Lib..strings, DTS.Lib..treeobj, DTS.Lib..utils.
-
- DTS.Lib_strings (or DTS.Lib..strings) contains only a single source file,
- StringUtils.c. StringUtils.c contains some useful string functions. It allows
- you to do various functions that are supplied by sprintf. The amount of code
- you have to link in when you choose sprintf is quite large, however. The
- supplied string functions allow you to easily format and parse string data
- without linking a large chunk of code.
-
- It is suggested that you link the StringUtils executable into the code segment
- that contains main(). This will guarantee that the string functions are always
- resident in memory when you call them. (MPW make files and Think projects are
- already set up this way.) If the code isn't in ram when you make a string
- call, the loading of the code may cause memory to move or compress. This
- is fine, unless you passed a pointer into an unlocked handle as a string
- pointer. If you point into an unlocked handle, simply calling the function can
- move memory, unless it is guaranteed that the code is already in memory. By
- linking it into the same code segment as main(), you guarantee that it is
- always in memory. (All of the sample applications already do this.)
-
-
- Various libraries can be used without committing to the entire DTS.Lib application
- framework. You can write your own application completely from scratch and then
- call any of the code in the above-mentioned files anytime you want.
-
- Each of the above files has useful stand-alone code you will probably want to
- look at, even if you choose not to use the DTS.Lib application framework.
-
- If you choose to use the DTS.Lib application framework, (from now on called
- DTS.framework), very much of the application development tasks are already done
- for you.
-
- DTS.framework currently supports:
- Multiple windows
- Multiple document types
- File I/O
- Apple Events
- Window/document scrolling
- Hierarchical document architecture
- Infinite undo (undos can optionally be saved with the document)
- Floating palettes
- Viewing window for displaying document objects
- Other document-specific tasks, such as mouseDowns, keyDowns, cursors, etc.
-
- What tasks DTS.framework doesn't directly supply can probably be found in the
- DTS utilities, so between the two, many of the aspects of Mac application
- development are already complete.
-
-
- So what is the minimum you have to know to get started?
-
- Above, we discussed the various components of the DTS library code. So now we
- need to know the easiest way to use this code. The easiest way is to make a
- copy of the sample application Wannabe, and start adding code. Consider Wannabe
- an application shell which already correctly uses the DTS.framework and
- utilities. All that is missing is the code that is specific to your
- application. Everything that is generic is already in there.
-
- The best place to start explaining is how to manage documents. We will start
- with a code snippet from Wannabe:
-
- switch (menuItem) {
- case kStdNew:
- err = NewDocument(&frHndl, gAppWindowType, true);
- if (!err) {
- err = DoNewWindow(frHndl, nil, FrontWindow(), (WindowPtr)-1);
- if (err)
- DisposeDocument(frHndl);
- }
- if (err)
- CenteredAlert(rErrorAlert, nil, (ModalFilterProcPtr)AlertFilter);
- break;
- .
- .
- .
-
-
- This is right out of the file Menu.c We get to this code by the user choosing
- "New" from the file menu. The first thing we do is to try to create the new
- document. This is done with the DTS.framework call NewDocument().
-
- All NewDocument() does, if successful, is to create a handle to the size of the
- document structure, and initialize many of the fields. If it succeeds, frHndl
- is set to that handle. If it fails, frHndl is set to nil, and the reason for
- the failure is returned into err.
-
- As stated above, DTS.framework can support multiple document types. The type
- of the document we are trying to create is docFileType, which is an OSType.
- In Wannabe, docFileType is defined as follows:
- #define docFileType 'DUMD'
-
- 'DUMD' stands for DUMb Document. I consider it dumb because when the document
- is saved, it is saved with no content. The DTS.framework very carefully saves
- nothing. You can then open nothing later. You can print nothing, etc.
- Hopefully, you will remedy this situation.
-
- The third parameter to NewDocument() tells NewDocument() that you want to
- create a document with a new "Untitled #" style of window title. The true
- indicates to DTS.framework that you want this document to have an untitled
- number one greater then the last time that NewDocument() was called. No big
- deal, but I figured you would be curious at this point.
-
- If NewDocument() succeeds, you now have a new document that has no window.
- Once the document is successfully created, you then want to give the document
- a window. This is done with the DoNewWindow() call. You pass in the frHndl
- that NewDocument() created.
-
- One of the fields in the frHndl that is initialized is the fileState.attributes
- field. This field describes various window attributes if a window is opened for
- this document. The default attributes is kept in the global gAppWindowAttr.
- If you don't like these attributes, you can change them in the frHndl after it
- initialized and before you give the document a window.
-
- The various values for the attribute field are:
-
- #define kwGrowIcon 0x00000001L
- #define kwHScroll 0x00000002L
- #define kwHScrollLessGrow 0x00000006L
- #define kwVScroll 0x00000008L
- #define kwVScrollLessGrow 0x00000018L
- #define kwVisible 0x00000020L
- #define kwOpenAtOldLoc 0x00000040L
- #define kwDoFirstClick 0x00000080L
- #define kwHideOnClose 0x00000100L
- #define kwIsDocument 0
- #define kwIsPalette 0x00000200L
- #define kwIsModalDialog 0x00000400L
- #define kwDefaultDocHeader 0x00000800L
- #define kwHeaderIsResource 0x00001000L
- #define kwRuntimeOnlyDoc 0x00002000L
- #define kwAutoNew 0x00004000L
- #define kwDefaultDocType 0x00008000L
- #define kwColorMonitor 0x00010000L
- #define kwSecondaryMonitor 0x00020000L
- #define kwStaggerWindow 0x00040000L
- #define kwCenterWindow 0x00080000L
- #define kwSameMonitor 0x00100000L
-
- So, if you want to create a window with document scrollbars, you just set a few
- bits in the attribute field, and you're done. And yes, kwOpenAtOldLoc
- automatically opens the window wherever the user had it when it was closed,
- and yes it makes sure that it is at least partially visible on some monitor.
- You can find more information elsewhere.
-
- In the Wannabe file File.c, you get called after the defaults are placed in the frHndl.
- The function InitDocument() is called. You can check the OSType of the frHndl, and
- then make adjustments based on the various document types in your application.
- To dereference to the attributes field, given an frHndl, do like so:
- (*frHndl)->fileState.attributes = blah;
-
- Other changes can be made to the frHndl at this point. This is where you state that you
- don't want certain default behaviors for the document. (More on this later.)
-
-
- Assuming that the frHndl got created successfully, you then probably want to give the document
- a window. Going back to our code:
-
- err = NewDocument(&frHndl, gAppWindowType, true);
- if (!err) {
- err = DoNewWindow(frHndl, nil, FrontWindow(), (WindowPtr)-1);
- if (err)
- DisposeDocument(frHndl);
- }
- if (err)
- CenteredAlert(rErrorAlert, nil, (ModalFilterProcPtr)AlertFilter);
- break;
-
- We call DoNewWindow(), and we pass it the successfully created (and possibly modified)
- frHndl. DoNewWindow() looks up various fields in the frHndl, and creates the window
- accordingly.
-
-
- The prototype for DoNewWindow is:
-
- OSErr DoNewWindow(FileRecHndl frHndl, WindowPtr *retWindow,
- WindowPtr relatedWindow, WindowPtr behind);
-
- If DoNewWindow() successfully creates a window, the window is returned in
- retWindow, if retWindow is not nil. Many times you don't really care what the
- window that was created is. In this case, if it is successful, it is now the
- new top window. If the user clicks on it, then you care. Here you don't.
-
- If DoNewWindow() was unsuccessful at creating the window, you now are left with
- a document, frHndl, that has no window. If this occurs, you want to dispose of
- the document, and report an error.
-
- That's all there is to it. Oh, the remaining parameters to DoNewWindow():
- relatedWindow: Create the window on the same monitor that holds most of
- this window. If nil is passed in, create the window on the
- main monitor.
- behind: Create the window behind the window indicated.
- -1 puts it on top.
-
-
- Now that we understand document creation and window creation using
- DTS.framework, here's the rest of the story:
-
-
- As mentioned above, NewDocument() returns a handle. This handle is your
- reference to the document, thus my favorite variable name for one os these
- things: frHndl (file reference handle). You don't have to worry about
- associating the frHndl with the window that was created for it. DoNewWindow()
- stores the frHndl in the window's refcon field. Also, you don't have to worry
- about which window belongs to a particular frHndl, as the window is stored in
- the frHndl, as well. The document and its associated window both point to one
- another. (You got one, you got the other -- cool.)
-
-
- Basically, everything that goes on with documents goes on inside the frHndl.
- The frHndl is a handle that holds the structure for the document. Most of this
- structure looks like this:
-
- typedef struct {
- OSType sfType;
- Boolean defaultDoc;
- Movie movie;
- short movieResID;
- short movieFlags;
- Boolean movieDataRefWasChanged;
- Boolean docDirty;
- long modNum;
- long modTick;
- Boolean readOnly;
- short refNum;
- short resRefNum;
- FSSpec fss;
- short windowID;
- WindowPtr window;
- PositionWndProcPtr getDocWindow;
- CalcFrameRgnProcPtr calcFrameRgnProc;
- ContentClickProcPtr contentClickProc;
- ContentKeyProcPtr contentKeyProc;
- DrawFrameProcPtr drawFrameProc;
- FreeDocumentProcPtr freeDocumentProc;
- FreeWindowProcPtr freeWindowProc;
- ImageProcPtr imageProc;
- InitContentProcPtr initContentProc;
- ReadDocumentProcPtr readDocumentProc;
- ReadDocumentHeaderProcPtr readDocumentHeaderProc;
- ResizeContentProcPtr resizeContentProc;
- ScrollFrameProcPtr scrollFrameProc;
- UndoFixupProcPtr undoFixupProc;
- WindowCursorProcPtr windowCursorProc;
- WriteDocumentProcPtr writeDocumentProc;
- WriteDocumentHeaderProcPtr writeDocumentHeaderProc;
- AdjustMenuItemsProcPtr adjustMenuItemsProc;
- DoMenuItemProcPtr doMenuItemProc;
- long attributes; /* Here down is window content information. */
- Rect windowSizeBounds;
- ControlHandle hScroll;
- ControlHandle vScroll;
- short hScrollIndent;
- short vScrollIndent;
- short leftSidebar;
- short topSidebar;
- short hArrowVal;
- short vArrowVal;
- short hPageVal;
- short vPageVal;
- } FileStateRec;
-
-
- Here's some explanation of some of the fields:
-
- sfType: The type of the document you passed to NewDocument() is
- stored here.
-
- defaultDoc: Used by application framework to determine if View Hierarchy
- facility can be used to view the document.
-
- movie: These 4 fields are used together. If the document type used
- movieResID: is of type 'MooV' for either NewDocument or OpenDocument,
- movieFlags: then these fields are used to keep track of the movie information
- movieDataRefWasChanged: for the document. In addition to this information, the meaning of
- the field refNum is extended somewhat. Here's the deal:
- Movie files are generally resource-fork-based, but this isn't
- always the case. The movie file can be flattened and made to
- be data-fork-based. This is so MS-DOS machines can handle the
- movie file.
- To be consistent with regular document files, the resRefNum
- returned by OpenMovieFile (which isn't always a resource refNum)
- is kept in the field refNum. Normally refNum represents a data
- fork, but in the case of movies, it may be either a data fork or
- resource fork reference number.
- For regular documents, if you wish to use the resource fork, you
- use the calls UseDocResFile and CloseDocResFile. These still work
- for movies, whether the movie is data-fork- or resource-fork-based.
- If the movie is resource fork based, then UseDocResFile simply
- copies the refNum field into the resRefNum field. This is the
- correct thing to do, as the resource is already opened for the movie.
- CloseDocResFile looks at the refNum and resRefNum fields. If they
- are the same, then it simply sets resRefNum to kInvalidRefNum.
- This is all that is necessary to "close" the resource fork, as you
- really don't want it to close, since it was opened for the movie.
- All of this behavior is simply to allow the various calls to do the
- appropriate thing when the document is a movie. You should be able
- to handle movie files just like regular document files.
-
- docDirty: Used by the application framework to determine if a
- "Save before closing" dialog should be displayed.
-
- modNum: This is incremented whenever you call SetDocDirty(). Some
- applications find this information useful. Most don't care.
-
- modTick: Tick count of last modification. (See modNum).
-
- readOnly: True if document is opened read-only. DTS.framework sets
- this if the document that is selected by the user can't be
- openeded with read-write access. DTS.framework asks the
- user if it is okay to open it read-only.
-
- refNum: The file reference number (0 if it is a new document.)
-
- resRefNum: Reference number of the resource fork of the document file.
- Unless you specifically use the resource fork, the
- document's resource fork isn't opened.
-
- fss The FSSpec for the open file (if there is one open for this
- document.)
-
- windowID: The resource ID of the 'WIND' resource to be used when
- DoNewDocument() is called to give this document a window.
- windowID is initialized to rWindow, which is defined to be
- 128. If you want a different 'WIND' resource for this
- document, set this field after calling NewDocument(), and
- prior to calling DoNewWindow().
-
- window: When you call DoNewWindow(), if it succeeds at creating a
- window, it places the window pointer here.
-
- The next fields are the procedure pointers that determine the behavior of the
- document. These fields are initialized to point to the following functions:
-
- For documents:
-
- getDocWindow: GetStaggeredWindow
- calcFrameRgnProc: CalcFrameRgn
- contentClickProc: ContentClick
- contentKeyProc: ContentKey
- drawFrameProc: DrawFrame
- freeDocumentProc: FreeDocument
- freeWindowProc: FreeWindow
- imageProc: ImageDocument
- initContentProc: InitContent
- readDocumentProc: ReadDocument
- readDocumentHeaderProc: nil
- resizeContentProc: ResizeContent
- scrollFrameProc: ScrollFrame
- undoFixupProc: UndoFixup
- windowCursorProc: WindowCursor
- writeDocumentProc: WriteDocument
- writeDocumentHeaderProc: nil
- adjustMenuItemsProc: AdjustMenuItems
- doMenuItemProc: DoMenuItem
-
-
- For dialogs:
-
- getDocWindow: GetStaggeredWindow
- calcFrameRgnProc: DialogCalcFrameRgn
- contentClickProc: DialogContentClick
- contentKeyProc: DialogContentKey
- drawFrameProc: DialogDrawFrame
- freeDocumentProc: DialogFreeDocument
- freeWindowProc: DialogFreeWindow
- imageProc: DialogImageDocument
- initContentProc: DialogInitContent
- readDocumentProc: nil
- readDocumentHeaderProc: nil
- resizeContentProc: DialogResizeContent
- scrollFrameProc: DialogScrollFrame
- undoFixupProc: DialogUndoFixup
- windowCursorProc: DialogWindowCursor
- writeDocumentProc: nil
- writeDocumentHeaderProc: nil
- adjustMenuItemsProc: DialogAdjustMenuItems
- doMenuItemProc: DialogDoMenuItem
-
-
- For palettes:
-
- getDocWindow: GetStaggeredWindow
- calcFrameRgnProc: PaletteCalcFrameRgn
- contentClickProc: PaletteContentClick
- contentKeyProc: PaletteContentKey
- drawFrameProc: PaletteDrawFrame
- freeDocumentProc: PaletteFreeDocument
- freeWindowProc: PaletteFreeWindow
- imageProc: PaletteImageDocument
- initContentProc: PaletteInitContent
- readDocumentProc: nil
- readDocumentHeaderProc: nil
- resizeContentProc: PaletteResizeContent
- scrollFrameProc: PaletteScrollFrame
- undoFixupProc: PaletteUndoFixup
- windowCursorProc: PaletteWindowCursor
- writeDocumentProc: nil
- writeDocumentHeaderProc: nil
- adjustMenuItemsProc: AdjustMenuItems
- doMenuItemProc: DoMenuItem
-
- Note that unless gAppWindowAttr is set for a dialog or palette document/window,
- all documents will be created with the first set of procPtrs.
-
- (AppsToGo allows you to set a document/window as a dialog or palette, and if
- you are using AppsToGo for development, then gAppWindowAttr isn't used. Instead
- the attributes for the document/window description set with AppsToGo are.
- AppsToGo allows you to indicate if a document/window should be a dialog or palette.)
-
- With the exception of the function GetStaggeredWindow, all of the above
- functions are part of the application shell Wannabe. GetStaggeredWindow is a
- function in the DTS utilities that opens a window "staggered" from existing
- windows. This allows some of the window to be exposed so that the user can
- easily click on the window and bring it to the front.
-
- All of the other functions are part of your application. They get called by
- the application framework at appropriate times. They don't get called at some
- magic moment. They get called when your application calls DTS.framework to do
- something. DTS.framework does the generic work. The application-specific work
- it can't do, and so it calls your application to do that part.
-
- Even for something as supposedly application-specific as drawing the window
- content, you still will call DTS.framework to get the job started. You will
- call the function DoImageDocument(). You pass DoImageDocument() a file
- reference handle (frHndl). DoImageDocument() dereferences the frHndl and
- gets the value at the field imageProc. If imageProc is nil, then there is
- no imaging procedure for this document, and DoImageDocument() just returns.
- If there is a procedure pointer stored in the field imageProc, then that
- code is called.
-
- There is no particular magic happening here. The DoImageDocument() code looks
- just as you would expect:
-
- OSErr DoImageDocument(FileRecHndl frHndl)
- {
- ImageProcPtr proc;
- OSErr err;
-
- err = noErr;
- if (proc = (*frHndl)->fileState.imageProc) err = (*proc)(frHndl);
- return(err);
- }
-
- If there is an imaging procedure in the file reference, call it. Otherwise
- it just returns noErr.
-
- Initially imageProc has a value. The initial value is ImageDocument. The
- ImageDocument() function is located in the Wannabe file Window.c. If your
- application only has a single document type, then just place your document
- imaging code inside the stub function ImageDocument(). It's that easy.
-
- For the most part, all of the procedure pointers in the frHndl have a "Do"
- function. For example: If you want to draw the "frame" portion of a window,
- you wouldn't call DrawFrame(). You would call DoDrawFrame(). DoDrawFrame()
- will look up the procedure pointer, and if it is not nil, it will call it,
- just like DoImageDocument() does.
-
-
- So how exactly does a file reference handle get created? Again, nothing
- magic, but there are a number of steps that need to be understood.
-
-
- The creation of an frHndl is invoked by the application. The application
- calls either NewDocument() or OpenDocument(), discussed above. OpenDocument()
- calls NewDocument(), so in either case, you are actually calling NewDocument().
-
- I think the easiest way to explain what NewDocument() does is to show the code.
- The two key places are marked with •••. The explanation follows. Here's most of the code:
-
-
- OSErr NewDocument(FileRecHndl *returnHndl, OSType sftype, Boolean incTitleNum)
- {
- long size;
- FileRecHndl frHndl;
- FileRecPtr frPtr;
- Str255 untitled;
- StringPtr pstr;
- OSErr err;
- short i;
- Movie movie;
- TreeObjHndl wobj;
- PositionWndProcPtr windowPlacementProc;
- static short untitledCount;
-
- if (returnHndl)
- *returnHndl = nil;
-
- if (!sftype) {
- if (!returnHndl)
- untitledCount = 0; /* API trick to allow you to reset the document count. */
- return(noErr);
- }
-
- err = memFullErr; /* Assume that we will fail. */
-
- ••• size = InitDocumentSize(sftype);
- /* Call the application and ask it how big the frHndl should be for
- ** this document type. We can't know, so we'll ask. */
-
- if (frHndl = (FileRecHndl)NewHandleClear(size)) {
- /* Create (or try to) the frHndl, initialized to all 0's */
-
- if (returnHndl)
- *returnHndl = frHndl;
-
- .
- .
- . This varies, depending on if you are using AppsToGo.
- .
- .
-
- /* Below we fill in the various fields with defaults for the frHndl. */
-
- (*frHndl)->fileState.modNum = GetModNum();
- /* In case GetModNum gets moved to another code segment, set this value
- ** prior to dereferencing frHndl into frPtr. */
-
- frPtr = *frHndl;
- frPtr->fileState.sfType = sftype;
- frPtr->fileState.modTick = TickCount();
- frPtr->fileState.refNum = kInvalRefNum;
- frPtr->fileState.resRefNum = kInvalRefNum;
- frPtr->fileState.fss.vRefNum = kInvalVRefNum;
- frPtr->fileState.windowID = rWindow;
- /* The above sets the fileState constants for the document. Note
- ** that we use a default 'WIND' ID for the expected window resource.
- ** This can be changed later, if the default isn't good enough. */
-
- if (wobj) {
- frPtr->fileState.windowID = mDerefWFMT(wobj)->windowID;
- frPtr->fileState.attributes = mDerefWFMT(wobj)->attributes;
- frPtr->fileState.hScrollIndent = mDerefWFMT(wobj)->hScrollIndent;
- frPtr->fileState.vScrollIndent = mDerefWFMT(wobj)->vScrollIndent;
- frPtr->fileState.leftSidebar = mDerefWFMT(wobj)->leftSidebar;
- frPtr->fileState.topSidebar = mDerefWFMT(wobj)->topSidebar;
- /* Set window attributes as described in resource. */
- }
- else
- frPtr->fileState.attributes = gAppWindowAttr;
- /* Set window attributes for the main document type. If the document
- ** is not the main type, then the application's InitDocument function
- ** will have to change it. */
-
- frPtr->fileState.getDocWindow = windowPlacementProc;
- frPtr->fileState.adjustMenuItemsProc = AdjustMenuItems;
- frPtr->fileState.doMenuItemProc = DoMenuItem;
- switch (frPtr->fileState.attributes & (kwIsPalette | kwIsModalDialog)) {
- case kwIsPalette:
- frPtr->fileState.calcFrameRgnProc = PaletteCalcFrameRgn;
- frPtr->fileState.contentClickProc = PaletteContentClick;
- frPtr->fileState.contentKeyProc = PaletteContentKey;
- frPtr->fileState.drawFrameProc = PaletteDrawFrame;
- frPtr->fileState.freeDocumentProc = PaletteFreeDocument;
- frPtr->fileState.freeWindowProc = PaletteFreeWindow;
- frPtr->fileState.imageProc = PaletteImageDocument;
- frPtr->fileState.initContentProc = PaletteInitContent;
- frPtr->fileState.readDocumentProc = nil;
- frPtr->fileState.resizeContentProc = PaletteResizeContent;
- frPtr->fileState.scrollFrameProc = PaletteScrollFrame;
- frPtr->fileState.undoFixupProc = PaletteUndoFixup;
- frPtr->fileState.windowCursorProc = PaletteWindowCursor;
- frPtr->fileState.writeDocumentProc = nil;
- break;
- case kwIsModalDialog:
- frPtr->fileState.calcFrameRgnProc = DialogCalcFrameRgn;
- frPtr->fileState.contentClickProc = DialogContentClick;
- frPtr->fileState.contentKeyProc = DialogContentKey;
- frPtr->fileState.drawFrameProc = DialogDrawFrame;
- frPtr->fileState.freeDocumentProc = DialogFreeDocument;
- frPtr->fileState.freeWindowProc = DialogFreeWindow;
- frPtr->fileState.imageProc = DialogImageDocument;
- frPtr->fileState.initContentProc = DialogInitContent;
- frPtr->fileState.readDocumentProc = nil;
- frPtr->fileState.resizeContentProc = DialogResizeContent;
- frPtr->fileState.scrollFrameProc = DialogScrollFrame;
- frPtr->fileState.undoFixupProc = DialogUndoFixup;
- frPtr->fileState.windowCursorProc = DialogWindowCursor;
- frPtr->fileState.writeDocumentProc = nil;
- frPtr->fileState.adjustMenuItemsProc = DialogAdjustMenuItems;
- frPtr->fileState.doMenuItemProc = DialogDoMenuItem;
- break;
- default:
- frPtr->fileState.calcFrameRgnProc = CalcFrameRgn;
- frPtr->fileState.contentClickProc = ContentClick;
- frPtr->fileState.contentKeyProc = ContentKey;
- frPtr->fileState.drawFrameProc = DrawFrame;
- frPtr->fileState.freeDocumentProc = FreeDocument;
- frPtr->fileState.freeWindowProc = FreeWindow;
- frPtr->fileState.imageProc = ImageDocument;
- frPtr->fileState.initContentProc = InitContent;
- frPtr->fileState.readDocumentProc = ReadDocument;
- frPtr->fileState.resizeContentProc = ResizeContent;
- frPtr->fileState.scrollFrameProc = ScrollFrame;
- frPtr->fileState.undoFixupProc = UndoFixup;
- frPtr->fileState.windowCursorProc = WindowCursor;
- frPtr->fileState.writeDocumentProc = WriteDocument;
- break;
- }
-
- frPtr->fileState.windowSizeBounds.left = kMinWindowWidth;
- frPtr->fileState.windowSizeBounds.top = kMinWindowHeight;
- frPtr->fileState.windowSizeBounds.right = kMaxWindowWidth;
- frPtr->fileState.windowSizeBounds.bottom = kMaxWindowHeight;
- /* Default min/max window size for growIcon. */
-
- pstr = frPtr->fileState.fss.name;
- pcpy(pstr, untitled);
- if (pstr[0]) {
- if (incTitleNum)
- ++untitledCount;
- pcatdec(pstr, untitledCount);
- /* Create the default document title. It is stored in the FSSpec,
- ** as we can't place it in the window title. We don't have a window
- ** yet to "title". This will happen later. The title is gotten from
- ** the FSSpec, so we are effectively done. */
- }
-
- ••• err = InitDocument(frHndl);
- /* Call the application for any additional document initialization.
- ** Other handles may need to be allocated. The default values above
- ** may be incorrect for certain document types. This gives the
- ** application a chance to change any defaults that are incorrect. */
-
- if ((*frHndl)->fileState.sfType == MovieFileType) {
- movie = NewMovie(newMovieActive);
- err = GetMoviesError();
- if (!err) {
- ClearMovieChanged(movie);
- (*frHndl)->fileState.movie = movie;
- }
- }
-
- if (err) {
- DisposeHandle((Handle)frHndl);
- if (returnHndl)
- *returnHndl = nil;
- /* If the application couldn't complete the document
- ** initialization, pitch the handle. */
- }
- }
-
- return(err);
- }
-
-
- Note the two lines marked with •••. These two points the framework function NewDocument()
- calls the application, which are:
-
- 1) The call to InitDocumentSize()
- 2) The call to InitDocument()
-
-
- These two functions are part of your application. (They are in the source file
- File.c) The first is called just so NewDocument() knows how big a handle to
- create for the document reference. If your application has only one document
- type, then this function will simply return a constant. If your application
- has multiple document types, you will want to return the size that corresponds
- to the document type (OSType).
-
- Your application's function InitDocumentSize() is passed the OSType. At this
- point, the framework has only piece of information on the document-to-be.
- Until the application framework knows the size, it can't create the file
- reference handle. You tell the application framework how big to create the
- frHndl. It creates it to the requested size, and initializes it to 0's and
- default values.
-
- Once it is initialized to defaults, NewDocument() calls your application, giving
- it a chance to change the default values. Only the frHndl is passed to
- InitDocument(). The frHndl from this point on is the document reference.
-
- If you have different document types, you will have to have a reasonably smart
- InitDocument() function. It will have to look at the document type, and then
- adjust the default values for the frHndl based on the document type. At this
- point, the document type is stored in the frHndl, in the field fileState.sfType.
-
- All of this is done prior to a window being created for the frHndl. This is
- important, as a number of the procedure pointers determine how the window will
- be created. You need to make customizations to the frHndl after its creation,
- and prior to the creation of the document's window.
-
- Of course, if your application only has one document type, then these defaults
- are just fine, and you won't have to worry about all of this stuff.
-
- The NewDocument() is pretty big, but that means that you don't have to worry about this
- stuff. The NewDocument() function is in charge of creating a functioning frHndl. The
- only thing your application has to do is to say how big it should be, and what defaults
- you didn't like.
-
-
- Let's revisit the code for a "New" menu item choice:
-
-
- err = NewDocument(&frHndl, gAppWindowType, true);
- if (!err) {
- err = DoNewWindow(frHndl, nil, FrontWindow(), (WindowPtr)-1);
- if (err)
- DisposeDocument(frHndl);
- }
- if (err)
- CenteredAlert(rErrorAlert, nil, (ModalFilterProcPtr)AlertFilter);
- break;
-
-
- We have explored what happens when NewDocument() is called. All that remains
- is the DoNewWindow() call.
-
-
- A window is opened for the frHndl. The values stored in the frHndl are used
- to determine how to open the window.
-
- In the sample call above, we create a window for the document frHndl. Here's
- what the parameters indicate:
-
- nil: Don't bother returning a reference to the window created.
- If we cared, we might pass in something like &newWindow.
- FrontWindow(): Window is created on the same monitor that holds most of the
- current front window.
- (WindowPtr)-1: Window is created as the front-most window.
-
-
- That's all there is to it. Now all you need is a way to hold your document
- data.
-
-
- Interestingly enough, DTS.framework has plenty of document support. You can
- use the hierarchical document architecture built into DTS.framework. It is
- very powerful. It handles file I/O, hierarchically related data, infinite
- undo, etc.
-
-
- The hierarchical document architecture is the default type of document. If
- you choose to use it, there are already calls supplied to create the initial
- default document. All you have to do is to call DefaultInitDocument() in
- your application's InitDocument() function. For all documents that use the
- hierarchical document architecture, simply call DefaultInitDocument() from
- within InitDocument().
-
- What DefaultInitDocument does is it creates a root object of type TreeObjHndl
- and places it in an expected location within the frHndl for the document.
- Consider this as a document within a document. The frHndl describes the
- run-time aspects of the document. The root object in the hierarchy begins
- the portion of the document that "persists", that is, saves to disk.
-
- The root object is just that. Yes, it has a data portion to it, but the main
- purpose of the root object is a holder of the "child" objects of the document.
-
- Whenever you make a change to the document, you will make it to some number of
- objects with the hierarchy. There are specific calls for this, and if you stick
- to these calls, infinite undo works automatically. There is a separate document
- describing the hierarchical document architecture. You will find it in the
- DTS.Lib folder. It is called "=Using TreeObj.c". It will detail everything
- you need to know about using the hierarchical document package.
-
- Let's review the chain of information for a document, startiong with the window:
-
- window: References the document (frHndl) associated with the window.
- The reference to the frHndl is kept in the window's refcon field.
-
- frHndl: References back to the window. This means that if you have either
- the window or the frHndl for a document, you can easily get the
- other.
-
- Also references the root hierarchy object. This is true only if you
- elect to use the TreeObj package in the DTS.framework. DTS.Draw
- uses this package heavily. Wannabe also uses it, but has no objects
- defined, as Wannabe is only an application shell.
-
-
- root: References the frHndl that contains it.
-
- Also references the undo root object that contains all of the
- information necessary to undo/redo edits to the document.
-
- References all of the children added to the root object.
-
-
- child: References all children it may in turn have.
-
- References the parent object. The root object can also be
- considered a child, but it is a child with no parent. This
- is reflected in the parent reference. If the parent reference
- is nil, then the object is a root object.
-
-
- This is a nice top-down view of where document/window information is stored.
- Here's some more information:
-
- frHndl information is NOT saved with the document. Any information you wish
- to be saved with the document must be contained in a hierarchy object.
-
- Changes to the root object can't be undone. The location in the document
- that is automatically kept for undo/redo operations is referenced by parent
- and child number. This locates you to any object within the hierarchy,
- except for one. The root object has no parent, so the location of the root
- object can not be indicated by parent/childNum.
-
- This means:
-
- • Any information tht is run-time, and you DON'T want saved to disk should be
- kept directly in the frHndl.
- • Any information that you want to keep with the document, but you don't want
- involved in undo/redo operations should be kept in the root object.
- • Any information that you want to be able to save and undo/redo in the
- document should be kept in a child below the level of the root object.
-
-
- Of course, all of the rules from the root object down imply that you are using
- the hierarchical document architecture. This is a completely optional package.
- All other rules continue to apply, whether or not you are using this package.
-
-
-
-
- With the exception of the root object, all of the above information is kept in
- the fileState portion of an frHndl. The root object begins the actual document,
- so it is actually in the document portion of an frHndl. Why am I talking about
- frHndl portions? Because an frHndl is comprised of three distinct structures.
- They are:
-
- FileStateRec: This is most adequately covered above.
- ConnectRec: This hasn't even been mentioned.
- TheDoc: This has been hinted at.
-
-
- The ConnectRec portion of a document record looks like this:
-
- typedef struct {
- long windowID[2]; /* Used to match up windows. */
- short endSendInfo; /* Above is send info. */
-
- Boolean connected; /* Flag showing we are connected. */
- AEAddressDesc remoteLoc; /* AppleEvent address of remote user. */
- Str32 remoteName; /* Name of user connected to. */
- Str32 remoteZone; /* Zone of user connected to. */
- Str32 remoteMachine; /* Machine name of user connected to. */
- short endLocalInfo; /* Above info is for 1 machine only. */
- } ConnectRec;
-
- The ConnectRec is used by DTS.framework to establish a connection to
- another application (or itself). This connection links two specific windows.
- Once connected, the boolean 'connected' is set true, and the other fields
- are filled in. The AEAddressDesc of whom you connected to is saved. The name,
- zone, and machine name are also kept. The endLocalInfo field is a reference
- point to determine the end of the ConnectRec at run-time. This is here in case
- there are additional fields added in the future. They would be added before the
- endLocalInfo field. Since the ConnectRec structure is DTS.framework's
- responsibility to maintain, you don't have to worry about this. (I do.)
-
-
- To see how to use the Apple Event facilities of DTS.framework, check out the
- application Wannabe. It uses these facilities.
-
-
-
- The final component of an frHndl is the document portion. It looks like this:
-
-
- typedef struct {
- DocHeaderInfo fhInfo; /* Doc hdr info (vers, prRec, window loc.) */
- TreeObjHndl root;
- } TheDoc;
-
-
- where DocHeaderInfo looks like this:
-
-
- typedef struct {
- short version; /* The file format version. */
- Boolean printRecValid; /* True if prRec has been created. */
- TPrint print; /* Print record for file. */
- Rect structureRect; /* Remember where window was when saved. */
- Rect contentRect; /* Remember where window was when saved. */
- Rect stdState; /* This and below rect used for saving */
- Rect userState; /* zoom information for window. */
- long hDocSize; /* hDocSize and vDocSize have to be saved */
- long vDocSize; /* with the document, or recalculated */
- /* when the file is opened so that the */
- /* window can be created the correct size. */
- short endDocHeaderInfo; /* End version, print, and window info. */
- } DocHeaderInfo;
-
-
- I suppose you figured that the document portion was your domain. Well, it is,
- kind of. Since DTS.framework supplies hierarchical document support, it has
- to have access to this portion of the frHndl, as well. You always need the
- fhInfo field of the document portion. Also, it must be the first field in
- the structure.
-
- Note the comment on the hDocSize and vDocSize fields. You can instruct the framework
- to save the DocHeaderInfo structure automatically with the document, in either the
- data fork or the resource fork. If you choose this option, then you don't have to
- worry about saving them or recalculating them. You will still have to initialize
- them to something appropriate whenever you create a new document, however.
-
- You optionally need the root field. If you are using the hierarchical document
- package, then you must have this field, and it must be the second field.
-
- Aside from these two rules, the structure is all yours. You can add as much as
- you wish to it.
-
-
- As has been described above, a single frHndl consists of three components.
- These are:
- 1) FileStateRec
- 2) ConnectRec
- 3) The document record.
-
- The way you assemble these parts into a single frHndl is like this:
-
-
- typedef struct FileRec {
- FileStateRec fileState; /* DTS.Lib expects this structure here. */
- ConnectRec connect; /* DTS.Lib expects this structure here. */
- union {
- TheDoc doc; /* Union in each document type here. */
- } d;
- } FileRec;
-
-
- This combines the three documents parts into a single structure. Note the use
- of union for the actual document portion. This is because you can have multiple
- document types within your application. Whenever you add a document type, just
- create a structure for it, and then union in this new structure into the FileRec
- definition. That's all there is to it (sort of).
-
- You have to remember to change the InitDocumentSize() and InitDocument()
- functions so that they can handle the new document type. Once this is done,
- you now have a new document type added to your application.
-
-
- I think this should give you a good feel of what you need to first, second, etc.
- Any other questions about using DTS.Lib and Wannabe should be answered in other
- read.me files. Of course, you can always look at the sample applications
- DTS.Draw and Wannabe for implementation-specific details.
-
-
-
- NOTE!! The above is very good information as to how Wannabe and the application framework
- actually function. However, a programming utility has been written that automates
- the above. This utility is called AppsToGo. Use it. The heck with code.
- AppsToGo allows you to use Wannabe and the framework without having to code for
- a while. Eventually you will have prototyped all the functionality possible and
- you will have to write some lines of code. However, AppsToGo allows you to
- defer coding for quite some time.
- The changes to Wannabe that you make with AppsToGo are permanent, and adding code
- to Wannabe doesn't prevent you form continuing to use AppsToGo. CHECK IT OUT!!
-
-
- Eric Soldan
-